﻿#pragma once

#include <vtkm/CellShape.h>
#include <vtkm/CellTraits.h>
#include <vtkm/Deprecated.h>
#include <vtkm/ErrorCode.h>
#include <vtkm/Types.h>
#include <vtkm/exec/FunctorBase.h>

struct TopoTables
{
	static constexpr vtkm::Int32 MAX_FACE_SIZE = 4;
	static constexpr vtkm::Int32 MAX_NUM_FACES = 6;

	VTKM_EXEC UInt8 NumFaces(UInt8 cell_shape_id) const
	{
        static constexpr UInt8 numFaces[vtkm::NUMBER_OF_CELL_SHAPES] =
        {
              0, //  0: CELL_SHAPE_EMPTY
              0, //  1: CELL_SHAPE_VERTEX
              0, //  2: Unused
              2, //  3: CELL_SHAPE_LINE
              0, //  4: CELL_SHAPE_POLY_LINE
              3, //  5: CELL_SHAPE_TRIANGLE
              0, //  6: Unused
              0, //  7: CELL_SHAPE_POLYGON
              0, //  8: Unused
              4, //  9: CELL_SHAPE_QUAD
              4, // 10: CELL_SHAPE_TETRA
              0, // 11: Unused
              6, // 12: CELL_SHAPE_HEXAHEDRON
              5, // 13: CELL_SHAPE_WEDGE
              5  // 14: CELL_SHAPE_PYRAMID
        };
        return numFaces[cell_shape_id];
	}

    VTKM_EXEC vtkm::Int32 NumPointsInFace(vtkm::Int32 cellShapeId, vtkm::Int32 faceIndex) const
    {
        VTKM_STATIC_CONSTEXPR_ARRAY vtkm::Int32
            numPointsInFace[vtkm::NUMBER_OF_CELL_SHAPES][MAX_NUM_FACES] = {
            // NumPointsInFace
            { -1, -1, -1, -1, -1, -1 }, //  0: CELL_SHAPE_EMPTY
            { -1, -1, -1, -1, -1, -1 }, //  1: CELL_SHAPE_VERTEX
            { -1, -1, -1, -1, -1, -1 }, //  2: Unused
            { 1, 1, -1, -1, -1, -1 }, //  3: CELL_SHAPE_LINE
            { 1, 1, 1, -1, -1, -1 }, //  4: CELL_SHAPE_POLY_LINE
            { 2, 2, 2, -1, -1, -1 }, //  5: CELL_SHAPE_TRIANGLE
            { -1, -1, -1, -1, -1, -1 }, //  6: Unused
            { -1, -1, -1, -1, -1, -1 }, //  7: CELL_SHAPE_POLYGON
            { -1, -1, -1, -1, -1, -1 }, //  8: Unused
            { 2, 2, 2, 2, -1, -1 }, //  9: CELL_SHAPE_QUAD
            { 3, 3, 3, 3, -1, -1 },     // 10: CELL_SHAPE_TETRA
            { -1, -1, -1, -1, -1, -1 }, // 11: Unused
            { 4, 4, 4, 4, 4, 4 },       // 12: CELL_SHAPE_HEXAHEDRON
            { 3, 3, 4, 4, 4, -1 },      // 13: CELL_SHAPE_WEDGE
            { 4, 3, 3, 3, 3, -1 }       // 14: CELL_SHAPE_PYRAMID
        };
        return numPointsInFace[cellShapeId][faceIndex];
    }

     VTKM_EXEC vtkm::Int32 PointsInFace(vtkm::Int32 cellShapeId,
                                     vtkm::Int32 faceIndex,
                                     vtkm::Int32 localPointIndex) const
  {
    // clang-format off
    VTKM_STATIC_CONSTEXPR_ARRAY vtkm::Int32 pointsInFace[vtkm::NUMBER_OF_CELL_SHAPES][MAX_NUM_FACES]
                                                  [MAX_FACE_SIZE] =
    {
      // PointsInFace
      //  0: CELL_SHAPE_EMPTY
      { { -1, -1, -1, -1 }, { -1, -1, -1, -1 }, { -1, -1, -1, -1 },
        { -1, -1, -1, -1 }, { -1, -1, -1, -1 }, { -1, -1, -1, -1 } },
      //  1: CELL_SHAPE_VERTEX
      { { -1, -1, -1, -1 }, { -1, -1, -1, -1 }, { -1, -1, -1, -1 },
        { -1, -1, -1, -1 }, { -1, -1, -1, -1 }, { -1, -1, -1, -1 } },
      //  2: Unused
      { { -1, -1, -1, -1 }, { -1, -1, -1, -1 }, { -1, -1, -1, -1 },
        { -1, -1, -1, -1 }, { -1, -1, -1, -1 }, { -1, -1, -1, -1 } },
      //  3: CELL_SHAPE_LINE
      { { 0, -1, -1, -1 }, { 1, -1, -1, -1 }, { -1, -1, -1, -1 },
        { -1, -1, -1, -1 }, { -1, -1, -1, -1 }, { -1, -1, -1, -1 } },
      //  4: CELL_SHAPE_POLY_LINE
      { { -1, -1, -1, -1 }, { -1, -1, -1, -1 }, { -1, -1, -1, -1 },
        { -1, -1, -1, -1 }, { -1, -1, -1, -1 }, { -1, -1, -1, -1 } },
      //  5: CELL_SHAPE_TRIANGLE
      { { 0, 1, -1, -1 }, { 1, 2, -1, -1 }, { 2, 0, -1, -1 },
        { -1, -1, -1, -1 }, { -1, -1, -1, -1 }, { -1, -1, -1, -1 } },
      //  6: Unused
      { { -1, -1, -1, -1 }, { -1, -1, -1, -1 }, { -1, -1, -1, -1 },
        { -1, -1, -1, -1 }, { -1, -1, -1, -1 }, { -1, -1, -1, -1 } },
      //  7: CELL_SHAPE_POLYGON
      { { -1, -1, -1, -1 }, { -1, -1, -1, -1 }, { -1, -1, -1, -1 },
        { -1, -1, -1, -1 }, { -1, -1, -1, -1 }, { -1, -1, -1, -1 } },
      //  8: Unused
      { { -1, -1, -1, -1 }, { -1, -1, -1, -1 }, { -1, -1, -1, -1 },
        { -1, -1, -1, -1 }, { -1, -1, -1, -1 }, { -1, -1, -1, -1 } },
      //  9: CELL_SHAPE_QUAD
      { { 0, 1, -1, -1 }, { 1, 2, -1, -1 }, { 2, 3, -1, -1 },
        { 3, 0, -1, -1 }, { -1, -1, -1, -1 }, { -1, -1, -1, -1 } },
      // 10: CELL_SHAPE_TETRA
      { { 0, 1, 3, -1 }, { 1, 2, 3, -1 }, { 2, 0, 3, -1 },
        { 0, 2, 1, -1 }, { -1, -1, -1, -1 }, { -1, -1, -1, -1 } },
      // 11: Unused
      { { -1, -1, -1, -1 }, { -1, -1, -1, -1 }, { -1, -1, -1, -1 },
        { -1, -1, -1, -1 }, { -1, -1, -1, -1 }, { -1, -1, -1, -1 } },
      // 12: CELL_SHAPE_HEXAHEDRON
      { { 0, 4, 7, 3 }, { 1, 2, 6, 5 }, { 0, 1, 5, 4 },
        { 3, 7, 6, 2 }, { 0, 3, 2, 1 }, { 4, 5, 6, 7 } },
      // 13: CELL_SHAPE_WEDGE
      { { 0, 1, 2, -1 }, { 3, 5, 4, -1 }, { 0, 3, 4, 1 },
        { 1, 4, 5, 2 },  { 2, 5, 3, 0 }, { -1, -1, -1, -1 } },
      // 14: CELL_SHAPE_PYRAMID
      { { 0, 3, 2, 1 }, { 0, 1, 4, -1 }, { 1, 2, 4, -1 },
        { 2, 3, 4, -1 }, { 3, 0, 4, -1 },{ -1, -1, -1, -1 } }
                                                          // clang-format on
                                                        };
    return pointsInFace[cellShapeId][faceIndex][localPointIndex];
  }

};

template <typename CellShapeTag>
static inline VTKM_EXEC vtkm::ErrorCode CellFaceNumberOfFaces(CellShapeTag shape,
    vtkm::IdComponent& result)
{
    (void)shape; //C4100 false positive workaround
    TopoTables table;
    result = table.NumFaces(shape.Id);
    return vtkm::ErrorCode::Success;
}


template <typename CellShapeTag>
static inline VTKM_EXEC vtkm::ErrorCode CellFaceNumberOfPoints(vtkm::IdComponent faceIndex,
    CellShapeTag shape,
    vtkm::IdComponent& result)
{
    if ((faceIndex < 0) || (faceIndex >= TopoTables::MAX_NUM_FACES))
    {
        result = -1;
        return vtkm::ErrorCode::InvalidFaceId;
    }

    vtkm::IdComponent numFaces;
    VTKM_RETURN_ON_ERROR(CellFaceNumberOfFaces(shape, numFaces));
    if (faceIndex >= numFaces)
    {
        result = -1;
        return vtkm::ErrorCode::InvalidFaceId;
    }
    TopoTables table;
    result = table.NumPointsInFace(shape.Id, faceIndex);
    return vtkm::ErrorCode::Success;
}

template <typename CellShapeTag>
static inline VTKM_EXEC vtkm::ErrorCode CellFaceShape(vtkm::IdComponent faceIndex,
    CellShapeTag shape,
    vtkm::UInt8& result)
{

    if ((faceIndex < 0) || (faceIndex >= TopoTables::MAX_NUM_FACES))
    {
        result = vtkm::CELL_SHAPE_EMPTY;
        return vtkm::ErrorCode::InvalidFaceId;
    }

    vtkm::IdComponent numFacePoints;
    VTKM_RETURN_ON_ERROR(CellFaceNumberOfPoints(faceIndex, shape, numFacePoints));
    switch (numFacePoints)
    {
    case 1:
        result = vtkm::CELL_SHAPE_VERTEX;
        break;
    case 2:
        result = vtkm::CELL_SHAPE_LINE;
        break;
    case 3:
        result = vtkm::CELL_SHAPE_TRIANGLE;
        break;
    case 4:
        result = vtkm::CELL_SHAPE_QUAD;
        break;
    default:
        result = vtkm::CELL_SHAPE_POLYGON;
        break;
    }
    return vtkm::ErrorCode::Success;
}

template <typename CellShapeTag>
static inline VTKM_EXEC vtkm::ErrorCode CellFaceLocalIndex(vtkm::IdComponent pointIndex,
    vtkm::IdComponent faceIndex,
    CellShapeTag shape,
    vtkm::IdComponent& result)
{
    vtkm::IdComponent numPointsInFace;
    result = -1;
    VTKM_RETURN_ON_ERROR(CellFaceNumberOfPoints(faceIndex, shape, numPointsInFace));
    if (numPointsInFace < 1)
    {
        // An invalid face. We should already have gotten an error from
        // CellFaceNumberOfPoints.
        return vtkm::ErrorCode::InvalidFaceId;
    }

    TopoTables table;
    result = table.PointsInFace(shape.Id, faceIndex, pointIndex);
    return vtkm::ErrorCode::Success;
}

template <typename CellShapeTag, typename GlobalPointIndicesVecType>
static inline VTKM_EXEC vtkm::ErrorCode CellFaceCanonicalId(
    vtkm::IdComponent faceIndex,
    CellShapeTag shape,
    const GlobalPointIndicesVecType& globalPointIndicesVec,
    vtkm::Id3& result)
{
    TopoTables table;

    vtkm::IdComponent numPointsInFace;
    result = { -1 };
    VTKM_RETURN_ON_ERROR(CellFaceNumberOfPoints(faceIndex, shape, numPointsInFace));
    if (numPointsInFace == 0)
    {
        // An invalid face. We should already have gotten an error from
        // CellFaceNumberOfPoints.
        return vtkm::ErrorCode::InvalidFaceId;
    }

    // 一维
    if (numPointsInFace == 1)
    {
        result[0] = globalPointIndicesVec[table.PointsInFace(shape.Id, faceIndex, 0)];
        result[1] = result[0];
        result[2] = result[0];
        return vtkm::ErrorCode::Success;
    }

    // 二维
    if (numPointsInFace == 2)
    {
        result[0] = globalPointIndicesVec[table.PointsInFace(shape.Id, faceIndex, 0)];
        result[1] = globalPointIndicesVec[table.PointsInFace(shape.Id, faceIndex, 1)];
        vtkm::Id temp;
        if (result[0] > result[1])
        {
            temp = result[0];
            result[0] = result[1];
            result[1] = temp;
        }
        result[2] = result[1];
        return vtkm::ErrorCode::Success;
    }

    // 三维
    result = vtkm::Id3(globalPointIndicesVec[table.PointsInFace(shape.Id, faceIndex, 0)],
        globalPointIndicesVec[table.PointsInFace(shape.Id, faceIndex, 1)],
        globalPointIndicesVec[table.PointsInFace(shape.Id, faceIndex, 2)]);
    vtkm::Id temp;
    if (result[0] > result[2])
    {
        temp = result[0];
        result[0] = result[2];
        result[2] = temp;
    }
    if (result[0] > result[1])
    {
        temp = result[0];
        result[0] = result[1];
        result[1] = temp;
    }
    if (result[1] > result[2])
    {
        temp = result[1];
        result[1] = result[2];
        result[2] = temp;
    }

    // Check the rest of the points to see if they are in the lowest 3
    for (vtkm::IdComponent pointIndex = 3; pointIndex < numPointsInFace; pointIndex++)
    {
        vtkm::Id nextPoint = globalPointIndicesVec[table.PointsInFace(shape.Id, faceIndex, pointIndex)];
        if (nextPoint < result[2])
        {
            if (nextPoint < result[1])
            {
                result[2] = result[1];
                if (nextPoint < result[0])
                {
                    result[1] = result[0];
                    result[0] = nextPoint;
                }
                else // nextPoint > P0, nextPoint < P1
                {
                    result[1] = nextPoint;
                }
            }
            else // nextPoint > P1, nextPoint < P2
            {
                result[2] = nextPoint;
            }
        }
        else // nextPoint > P2
        {
            // Do nothing. nextPoint not in top 3.
        }
    }

    return vtkm::ErrorCode::Success;
}